﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using MacomberMapSystem.Common.Serialization;
using System.Xml;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.CIM;
using System.Data.Common;
using System.IO;
using MacomberMapSystem.Common.User_Interfaces.Editors;
using System.Drawing.Design;

namespace MacomberMapSystem.Common.Processing
{
    /// <summary>
    /// This class performs audits against the model to ensure everythign is working properly
    /// </summary>
    public class CIM_Model_Audit : CIM_Processor
    {
        #region Enumerations

        /// <summary>
        /// The types of modes an audit can run in
        /// </summary>
        public enum enumAuditMode
        {
            /// <summary>Don't run the audit</summary>
            DontRun,
            /// <summary>Run the audit, and warn if a failure occurs</summary>
            WarnIfFailure,
            /// <summary>Run the audit, and stop processing if a failure occurs</summary>
            HaltIfFailure
        }
        #endregion

        #region Variable Declarations
        /// <summary>
        /// The XML summary file of the model (or leave blank to pull spc entries from the model)
        /// </summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Audits"), Description("The XML summary file of the model (or leave blank to pull spc entries from the model)"), Editor(typeof(MM_File_Browser), typeof(UITypeEditor))]
        public string ModelSummaryFile
        {
            get { return _ModelSummaryFile; }
            set { _ModelSummaryFile = value; }
        }
        private string _ModelSummaryFile;


        /// <summary>
        /// Whether individual mismatches should be written out
        /// </summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Audits"), Description("Whether individual mismatches should be written out"), DefaultValue(false)]
        public bool WriteIndividualMismatches
        {
            get { return _WriteIndividualMismatches; }
            set { _WriteIndividualMismatches = value; }
        }
        private bool _WriteIndividualMismatches = false;

        /// <summary>
        /// Make sure every substation has a latitude/longitude
        /// </summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Audits"), Description("Make sure every substation has a latitude/longitude")]
        public enumAuditMode SubstationCoordinates
        {
            get { return _SubstationCoordinates; }
            set { _SubstationCoordinates = value; }
        }
        private enumAuditMode _SubstationCoordinates;


        /// <summary>Make sure there are no unknown elements (outgoing links that don't exist in the model"</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Audits"), Description("Make sure there are no unknown elements (outgoing links that don't exist in the model")]
        public enumAuditMode NoUnknownLinks
        {
            get { return _NoUnknownLinks; }
            set { _NoUnknownLinks = value; }
        }
        private enumAuditMode _NoUnknownLinks;

        private enumAuditMode _ElementCounts;
        /// <summary>Make sure that the element counts match the target count</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Audits"), Description("Make sure that the element counts match the target count")]
        public enumAuditMode ElementCounts
        {
            get { return _ElementCounts; }
            set { _ElementCounts = value; }
        }

        private enumAuditMode _ElementLinks;

        /// <summary>Make sure that the element link counts match the target count</summary>
        [MM_Serializable_Target(MM_Serializable_Target.enumTargetType.FieldValue), Category("Audits"), Description("Make sure that the element link counts match the target count")]
        public enumAuditMode ElementLinks
        {
            get { return _ElementLinks; }
            set { _ElementLinks = value; }
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new model audit
        /// </summary>
        /// <param name="xElem"></param>
        /// <param name="NamedParameters"></param>
        /// <param name="Repository"></param>
        public CIM_Model_Audit(XmlElement xElem, Dictionary<String, Object> NamedParameters, MM_Repository Repository)
            : base(xElem, NamedParameters, Repository)
        {
        }
        #endregion

        /// <summary>
        /// Report the name of our proces
        /// </summary>
        public override string ProcessName
        {
            get { return "Model auditing"; }
        }


        /// <summary>
        /// Initiate our process
        /// </summary>
        /// <param name="DbConn"></param>
        public override enumProcessState InitiateProcess(MM_Database_Connector DbConn)
        {
            MM_Database_Model Model = NamedParameters["Model"] as MM_Database_Model;
            bool HaltProgress = false;
            bool HadWarning = false;

            using (System.IO.StreamWriter sW = new StreamWriter(Path.Combine(ProcessString(TargetFolder), ProcessString(TargetFileName))))
            {
                //Pull in our tally of instances and links
                Dictionary<String, int> InstanceCount = new Dictionary<string, int>();
                Dictionary<String, int> LinkCount = new Dictionary<string, int>();
                Dictionary<String, String> rdfIDNames = new Dictionary<string, string>();

                if (!String.IsNullOrEmpty(_ModelSummaryFile))
                {
                    //If we have a model summary file open it and pull in our data
                    XmlDocument xSummary = new XmlDocument();
                    xSummary.Load(ProcessString(_ModelSummaryFile));
                    foreach (XmlElement xElem in xSummary.DocumentElement.ChildNodes)
                        if (xElem.Name == "spc:Instances")
                        {
                            InstanceCount.Add(xElem["spc:Counts.nameSpace"].InnerText + ":" + xElem["spc:RootType.name"].InnerText, Convert.ToInt32(xElem["spc:Counts.count"].InnerText));
                            rdfIDNames.Add(xElem.Attributes["rdf:ID"].Value, xElem["spc:Counts.nameSpace"].InnerText + ":" + xElem["spc:RootType.name"].InnerText);
                        }
                        else if (xElem.Name == "spc:Links")
                            try
                            {
                                String LinkA = rdfIDNames[xElem["spc:Links.HasA_rdfRoleAType"].Attributes["rdf:resource"].Value.Substring(1)];
                                String LinkB = rdfIDNames[xElem["spc:Links.HasA_rdfRoleBType"].Attributes["rdf:resource"].Value.Substring(1)];
                                String LinkName = LinkA.CompareTo(LinkB) == -1 ? LinkA + ">" + LinkB : LinkB + ">" + LinkA;
                                if (LinkCount.ContainsKey(LinkName))
                                    LinkCount[LinkName] += Convert.ToInt32(xElem["spc:Counts.count"].InnerText);
                                else
                                    LinkCount.Add(LinkName, Convert.ToInt32(xElem["spc:Counts.count"].InnerText));
                            }
                            catch (Exception ex)
                            {
                                sW.WriteLine("ERROR in loading tallies: " + ex.Message + ": " + xElem.OuterXml.Replace("\n", ""));
                            }
                }
                else
                {
                    //Otherwise, pull instance counts from CIM
                    foreach (CIM_Element InstanceTally in Model.ElementsOfType("spc:Instance"))
                        InstanceCount.Add(InstanceTally["spc:Counts.nameSpace"] + ":" + InstanceTally["spc:RootType.name"], Convert.ToInt32(InstanceTally["spc:Counts.count"]));
                    foreach (CIM_Element LinkTally in Model.ElementsOfType("spc:Link"))
                        try
                        {
                            String LinkA = rdfIDNames[LinkTally["spc:Links.HasA_rdfRoleAType"].Substring(1)];
                            String LinkB = rdfIDNames[LinkTally["spc:Links.HasA_rdfRoleBType"].Substring(1)];
                            String LinkName = LinkA.CompareTo(LinkB) == -1 ? LinkA + ">" + LinkB : LinkB + ">" + LinkA;
                            if (LinkCount.ContainsKey(LinkName))
                                LinkCount[LinkName] += Convert.ToInt32(LinkTally["spc:Counts.count"]);
                            else
                                LinkCount.Add(LinkName, Convert.ToInt32(LinkTally["spc:Counts.count"]));
                        }
                        catch (Exception ex)
                        {
                            sW.WriteLine("ERROR in loading tallies,\"" + ex.Message + "\", " + LinkTally.XML.Replace("\n", "").Replace("\"", "\"\""));
                        }
                }

                this.Total = InstanceCount.Count + LinkCount.Count + Model.ElementsOfType("UNKNOWN").Length + Model.ElementsOfType("cim:Substation").Length;


                //First, run our unknown element  audit
                if (NoUnknownLinks == enumAuditMode.DontRun)
                    Current += Model.ElementsOfType("UNKNOWN").Length;
                else
                    foreach (CIM_Element Elem in Model.ElementsOfType("UNKNOWN"))
                    {
                        Current++;
                        HadWarning = true;
                        ProcessLog.AppendLine("Unknown element " + Elem.rdfID.ToString() + " was found in the model.");
                        sW.WriteLine("Unknown element," + Elem.rdfID.ToString());
                        if (NoUnknownLinks == enumAuditMode.HaltIfFailure)
                            HaltProgress = true;

                    }

                //Now, search for unknown substation one-lines
                if (SubstationCoordinates == enumAuditMode.DontRun)
                    Current += Model.ElementsOfType("cim:Substation").Length;
                else
                    foreach (CIM_Element SubElem in Model.ElementsOfType("cim:Substation"))
                    {
                        Current++;
                        if (bool.Parse(SubElem["etx:Substation.forNetwork"]) && bool.Parse(SubElem["etx:Substation.forScada"]))
                            if (!Repository.Db.Substations.ContainsKey(SubElem.rdfID))
                            {
                                HadWarning = true;
                                ProcessLog.AppendLine(String.Format("Substation {0} ({1} / {2}) is missing coordinates.", SubElem.Name, SubElem.rdfID.ToString(), SubElem.TEID));
                                sW.WriteLine("Missing coordinates," + SubElem.rdfID.ToString() + "," + SubElem.Name + "," + SubElem.LongName);
                                if (SubstationCoordinates == enumAuditMode.HaltIfFailure)
                                    HaltProgress = true;
                            }
                    }



                //Now, tally up our type counts, make sure everything matches
                int CurTally;
                if (ElementCounts == enumAuditMode.DontRun)
                    Current += InstanceCount.Count;
                else
                    foreach (KeyValuePair<String, int> kvp in InstanceCount)
                    {
                        Current++;
                        CIM_Element[] Elems = Model.ElementsOfType(kvp.Key);
                        if ((CurTally = Elems.Length) != kvp.Value)
                        {
                            HadWarning = true;
                            ProcessLog.AppendLine(string.Format("Type {0} count doesn't match. Actual={1:#,##0}, Expected={2:#,##0}", kvp.Key, CurTally, kvp.Value));
                            sW.WriteLine("Instance count mismatch," + kvp.Key + "," + CurTally.ToString() + "," + kvp.Value.ToString());
                            if (_WriteIndividualMismatches)
                                foreach (CIM_Element Elem in Elems)
                                    sW.WriteLine("  Element,{0},{1},{2}", Elem.rdfID, Elem.TypeName, Elem.Name);
                            if (ElementCounts == enumAuditMode.HaltIfFailure)
                                HaltProgress = true;
                        }
                    }

                //Now, tally up our type to element linkages, make sure everything matches.
                if (ElementLinks == enumAuditMode.DontRun)
                    Current += LinkCount.Count;
                else
                {
                    foreach (KeyValuePair<String, int> kvp in LinkCount)
                    {
                        Current++;
                        String[] SplStr = kvp.Key.Split('>');
                        List<CIM_Link> OutLinks = new List<CIM_Link>();
                        List<CIM_Link> InLinks = new List<CIM_Link>();

                        foreach (CIM_Element FromType in Model.ElementsOfType(SplStr[0]))
                        {
                            CIM_Link InLink = CIM_Link.FromElement(FromType);
                            foreach (CIM_Link ToType in FromType.InLinks)
                                if (ToType.TypeName.EndsWith(SplStr[1]))
                                    InLinks.AddRange(new CIM_Link[] { InLink, ToType });
                            foreach (CIM_Link ToType in FromType.OutLinks)
                                if (ToType.TypeName.EndsWith(SplStr[1]))
                                    OutLinks.AddRange(new CIM_Link[] { InLink, ToType });
                        }
                        if (InLinks.Count + OutLinks.Count != kvp.Value)
                        {
                            HadWarning = true;
                            ProcessLog.AppendLine(String.Format("The link count from {0} to {1} doesn't match: Actual={2:#,##0} ({3:#,##0} Out, {4:#,##0} In), Expected={5:#,##0}", SplStr[0], SplStr[1], InLinks.Count + OutLinks.Count, OutLinks.Count, InLinks.Count, kvp.Value));
                            sW.WriteLine("Link count mismatch," + SplStr[0] + "," + SplStr[1] + "," + (OutLinks.Count + InLinks.Count).ToString() + "," + OutLinks.Count.ToString() + "," + InLinks.Count.ToString() + "," + kvp.Value);
                            if (_WriteIndividualMismatches)
                            {
                                for (int a = 0; a < OutLinks.Count; a += 2)
                                    sW.WriteLine("  {0},{1},{2},{3},{4}", "  OutLink", OutLinks[a].rdfID, OutLinks[a].TypeName, OutLinks[a + 1].rdfID, OutLinks[a + 1].TypeName);
                                for (int a = 0; a < InLinks.Count; a += 2)
                                    sW.WriteLine("  {0},{1},{2},{3},{4}", "  InLink", InLinks[a].rdfID, InLinks[a].TypeName, InLinks[a + 1].rdfID, InLinks[a + 1].TypeName);
                            }
                            if (ElementLinks == enumAuditMode.HaltIfFailure)
                                HaltProgress = true;
                        }
                    }
                }


                //Now, if we need to halt, do so.
                if (HaltProgress)
                    NamedParameters["HaltProcessing"] = true;
            }
            return HadWarning ? enumProcessState.CompletedWithWarnings : enumProcessState.Completed;
        }
    }
}